﻿#region Copyright
/// <copyright>
/// Copyright (c) 2008 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Text;

namespace Osh.Iptv
{
	public class UdpTransportStream : PacketStream
	{
		private const int _maxUdpPacketSize = 2000;
		private const int _tsPacketSize = 188;

		private Socket _socket;
		private byte[] _udpBuffer;
		private int _udpBufferSize;
		private int _udpBufferPosition;

		public UdpTransportStream(IPAddress multicastAddress, int portNumber, IPAddress localAddress)
		{
			try
			{
				if(localAddress == IPAddress.Any)
					localAddress = GetLocalIPAddress(null);

				_udpBuffer = new byte[_maxUdpPacketSize];
				_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

				// Without selecting the interface we will get "Access denied" error on Vista with UAC when multiple itf available.
				byte[] bytes = localAddress.GetAddressBytes();
				_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, bytes);

				_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership,
					new MulticastOption(multicastAddress, localAddress)); // IPAddress.Any localAddress

				IPEndPoint localEndPoint = new IPEndPoint(localAddress, portNumber);
				_socket.Bind(localEndPoint);
			}
			catch(Exception e)
			{
				e.ToString();
				//Log.Error(Osh.Configuration.Config.Current.OshTraceSource, e.ToString());
			}
		}

		public Socket Socket
		{
			get { return _socket; }
		}

		#region PacketStreamBase
		protected override bool MoveToNextPacketInternal()
		{
			_udpBufferPosition += _tsPacketSize;
			if(_udpBufferPosition > _udpBufferSize)
			{
				try
				{
					do
					{
						_udpBufferSize = _socket.Receive(_udpBuffer);
						_udpBufferPosition = 0;
					}
					while(_udpBufferSize < _tsPacketSize);
				}
				catch(SocketException ex)
				{
					ex.ToString();
					//if(ex.ErrorCode == Win32.Sockets.WSAEMSGSIZE)
					return false;
				}
			}

			base.FillPacketBuffer(_udpBuffer, _udpBufferPosition, _tsPacketSize);
			return true;
		}

		public override void Close()
		{
			if(_socket != null)
			{
				_socket.Close();
				_socket = null;
			}
			base.Close();
		}
		#endregion PacketStreamBase

		public static IPAddress GetLocalIPAddress(string itfName)
		{
			NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
			foreach(NetworkInterface itf in interfaces)
			{
				if(!string.IsNullOrEmpty(itfName))
				{
					if(string.Equals(itf.Name, itfName, StringComparison.CurrentCultureIgnoreCase))
					{
						return itf.GetIPProperties().UnicastAddresses[0].Address;
					}
				}
				else
				{
					if(itf.OperationalStatus != OperationalStatus.Up)
						continue;

					IPInterfaceProperties ipProperties = itf.GetIPProperties();
					if(ipProperties.GatewayAddresses.Count == 0)
						continue;

					bool validGatewayFound = false;
					foreach(GatewayIPAddressInformation gatewayInfo in ipProperties.GatewayAddresses)
					{
						IPAddress gatewayAddress = gatewayInfo.Address;
						if(gatewayAddress.AddressFamily == AddressFamily.InterNetwork && !gatewayAddress.Equals(new IPAddress(0)))
						{
							validGatewayFound = true;
							break;
						}
					}
					if(!validGatewayFound)
						continue;

					foreach(UnicastIPAddressInformation ipInfo in ipProperties.UnicastAddresses)
					{
						if(ipInfo.Address.AddressFamily == AddressFamily.InterNetwork)
						{
							return ipInfo.Address;
						}
					}
				}
			}
			return IPAddress.None;
		}
	}
}
